(function(factory) {
	if (typeof define === 'function' && define.amd) {
		/*
		 In AMD environments, you will need to define an alias
		 to your selector engine. i.e. either zepto or jQuery.
		 */
		define(['$'], factory);
	} else {
		/*
		 Browser globals
		 */
		var selectorLibrary = window.Mobify && window.Mobify.$ || window.jQuery || window.Zepto;
		factory(selectorLibrary);
	}
})(function($) {

	var Utils = (function($) {
		var exports = {};
		var ua = navigator.userAgent;
		var has = $.support = $.support || {};

		$.extend($.support, {
			'touch': 'ontouchend' in document
		});

		/**
			Events (either touch or mouse)
		*/
		exports.events = (has.touch)
			? {down: 'touchstart', move: 'touchmove', up: 'touchend'}
			: {down: 'mousedown', move: 'mousemove', up: 'mouseup'};

		/**
			Returns the position of a mouse or touch event in (x, y)
			@function
			@param {Event} touch or mouse event
			@returns {Object} X and Y coordinates
		*/
		exports.getCursorPosition = (has.touch)
			? function(e) {e = e.originalEvent || e; return {x: e.touches[0].clientX, y: e.touches[0].clientY};}
			: function(e) {return {x: e.clientX, y: e.clientY};};


		/**
			Returns prefix property for current browser.
			@param {String} CSS Property Name
			@return {String} Detected CSS Property Name
		*/
		exports.getProperty = function(name) {
			var prefixes = ['Webkit', 'Moz', 'O', 'ms', ''];
			var testStyle = document.createElement('div').style;

			for (var i = 0; i < prefixes.length; ++i) {
				if (testStyle[prefixes[i] + name] !== undefined) {
					return prefixes[i] + name;
				}
			}

			// Not Supported
			return;
		};

		$.extend(has, {
			'transform': !!(exports.getProperty('Transform')),

			// Usage of transform3d on *android* would cause problems for input fields:
			// - https://coderwall.com/p/d5lmba
			// - http://static.trygve-lie.com/bugs/android_input/
			'transform3d': !!(window.WebKitCSSMatrix && 'm11' in new window.WebKitCSSMatrix() && !/android\s+[1-2]/i.test(ua))
		});

		// translateX(element, delta)
		// Moves the element by delta (px)
		var transformProperty = exports.getProperty('Transform');
		if (has.transform3d) {
			exports.translateX = function(element, delta) {
				if (typeof delta === 'number') {
					delta = delta + 'px';
				}
				element.style[transformProperty] = 'translate3d(' + delta  + ',0,0)';
			};
		} else if (has.transform) {
			exports.translateX = function(element, delta) {
				if (typeof delta === 'number') {
					delta = delta + 'px';
				}
				element.style[transformProperty] = 'translate(' + delta  + ',0)';
			};
		} else {
			exports.translateX = function(element, delta) {
				if (typeof delta === 'number') {
					delta = delta + 'px';
				}
				element.style.left = delta;
			};
		}

		// setTransitions
		var transitionProperty = exports.getProperty('Transition');
		var durationProperty = exports.getProperty('TransitionDuration');

		exports.setTransitions = function(element, enable) {
			if (enable) {
				element.style[durationProperty] = '';
			} else {
				element.style[durationProperty] = '0s';
			}
		};

		exports.onTransitionEnd = function($el, callback) {
			$el.one('transitionend webkitTransitionEnd otransitionend MSTransitionEnd', function(e) {
				var $target = $(e.target);
				if ($target.not($el).length === 0) {
					callback(e);
				}
			});
		};


		// Request Animation Frame
		// courtesy of @paul_irish
		exports.requestAnimationFrame = (function() {
			var prefixed = (window.requestAnimationFrame       ||
							window.webkitRequestAnimationFrame ||
							window.mozRequestAnimationFrame    ||
							window.oRequestAnimationFrame      ||
							window.msRequestAnimationFrame     ||
							function(callback) {
								window.setTimeout(callback, 1000 / 60);
							});

			var requestAnimationFrame = function() {
				prefixed.apply(window, arguments);
			};

			return requestAnimationFrame;
		})();

		return exports;

	})($);

	var Scooch = (function($, Utils) {
		var defaults = {
			dragRadius: 10,
			moveRadius: 20,
			animate: true,
			autoHideArrows: false,
			rightToLeft: false,
			infinite: false,
			autoplay: false,
			classPrefix: 'm-',
			classNames: {
				outer: 'scooch',
				inner: 'scooch-inner',
				item: 'item',
				center: 'center',
				touch: 'has-touch',
				dragging: 'dragging',
				active: 'active',
				inactive: 'inactive',
				fluid: 'fluid'
			}
		};
		var has = $.support;

		// Constructor
		var Scooch = function(element, options) {
			this.setOptions(options);
			this.initElements(element);

			if (this.options.infinite) {
				// Make infinite
				this.initLoop();
			}
			if (typeof this.options.autoplay === 'object') {
				this.initAutoplay();
			}
			this.initStartElement();

			this.initOffsets();
			this.initAnimation();

			this.bind();
			this.start();

			this._updateCallbacks = [];
		};

		// Expose Defaults
		Scooch.defaults = defaults;

		Scooch.prototype.setOptions = function(opts) {
			var options = this.options || $.extend({}, defaults, opts);

			/* classNames requires a deep copy */
			options.classNames = $.extend({}, options.classNames, opts.classNames || {});

			this.options = options;
		};

		Scooch.prototype.initElements = function(element) {
			this._index = 1;  // 1-based index

			this.element = element;
			this.$element = $(element);
			this.$inner = this.$element.find('.' + this._getClass('inner'));
			this.$items = this.$inner.children();

			this._length = this.$items.length;
			this._alignment = this.$element.hasClass(this._getClass('center')) ? 0.5 : 0;

			this._isFluid = this.$element.hasClass(this._getClass('fluid'));

			// Limits
			this._lockLeft = 1;
			this._lockRight = this._length;
		};

		Scooch.prototype.initStartElement = function() {
			this.$start = this.$inner.children().first();
			this.$current = this.$items.eq(this._index - 1);
		};

		Scooch.prototype.initLoop = function() {
			this._loopPrepend = 2;
			this._loopAppend = 2;

			for (var i = 0; i < this._loopPrepend; i++) {
				var $clone = this.$items.eq(this._length - 1 - i).clone();
				this.$inner.prepend($clone);
			}
			for (var i = 0; i < this._loopAppend; i++) {
				var $clone = this.$items.eq(i).clone();
				this.$inner.append($clone);
			}

			this._lockLeft = this._lockLeft - 1;
			this._lockRight = this._lockRight + 1;

			var self = this;
			this.$element.on('afterSlide', function() {
				var newIndex = self._index;

				// If one of the appended clones, go to the other side of the loop
				if (self._index < 1) {
					newIndex =  self._length;
				} else if (self._index > self._length) {
					newIndex =  1;
				} else {
					return;
				}

				self._index = newIndex;
				self.initStartElement();
				self.start();
			});
		};

		Scooch.prototype.initAutoplay = function() {
			// Using modulus to determine the next correct index. Always use the current
			// index and length in this calculation as the values can change.
			var self = this; // bind reference to this for later
			var moveScooch = function() {
				// scooch's index starts at 1, so increment after the modulus calculation.
				var newIndex = (self._index % self._length) + 1;

				self.move(newIndex);
			};

			if (this.options.autoplay.interval &&
				typeof this.options.autoplay.interval === 'number' &&
				this.options.autoplay.interval > 1
			) {
				// Always appear to be infinite until user interaction (if applicable)
				var previousAutoHideOption = self.options.autoHideArrows;
				self.options.autoHideArrows = false;

				self.timer = window.setInterval(moveScooch, this.options.autoplay.interval);

				if (typeof this.options.autoplay.cancelOnInteraction === 'boolean' &&
					this.options.autoplay.cancelOnInteraction
				) {
					this.$element.on('touchstart click mouseover', function() {
						window.clearInterval(self.timer);

						// Restore autoHideArrows option
						self.options.autoHideArrows = previousAutoHideOption;
						if (previousAutoHideOption) {
							self.hideArrows(self._index); // Refresh arrow states
						}
					});
				} else {
					// If no chance to cancel autoplay, this scooch will be infinite
					self.initLoop();
				}
			}
		};

		Scooch.prototype.initOffsets = function() {
			this._offsetDrag = 0;
		};

		Scooch.prototype.initAnimation = function() {
			this.animating = false;
			this.dragging = false;
			this._needsUpdate = false;
			this._enableAnimation();
		};


		Scooch.prototype._getClass = function(id) {
			return this.options.classPrefix + this.options.classNames[id];
		};


		Scooch.prototype._enableAnimation = function() {
			if (this.animating) {
				return;
			}

			Utils.setTransitions(this.$inner[0], true);
			this.$inner.removeClass(this._getClass('dragging'));
			this.animating = true;
		};

		Scooch.prototype._disableAnimation = function() {
			if (!this.animating) {
				return;
			}

			Utils.setTransitions(this.$inner[0], false);
			this.$inner.addClass(this._getClass('dragging'));
			this.animating = false;
		};

		Scooch.prototype.start = function() {
			this._disableAnimation();

			this.$element.trigger('beforeSlide', [this._index, this._index]);
			this.$element.trigger('afterSlide', [this._index, this._index]);

			this.update();
		};

		Scooch.prototype.refresh = function() {
			/* Call when number of items has changed (e.g. with AJAX) */
			this.$items = this.$inner.children( '.' + this._getClass('item'));
			this._length = this.$items.length;
			this._lockRight = this.$items.length;
			this.start();
		};

		Scooch.prototype.update = function(callback) {
			if (typeof callback !== 'undefined') {
				this._updateCallbacks.push(callback);
			}

			/* We throttle calls to the real `_update` for efficiency */
			if (this._needsUpdate) {
				return;
			}

			this._needsUpdate = true;

			var self = this;
			Utils.requestAnimationFrame(function() {
				self._update();

				setTimeout(function() {
					for (var i = 0, _len = self._updateCallbacks.length; i < _len; i++) {
						self._updateCallbacks[i].call(self);
					}
					self._updateCallbacks = [];
				}, 10);
			});
		};

		Scooch.prototype._update = function() {
			if (!this._needsUpdate) {
				return;
			}

			var $current = this.$current;
			var $start = this.$start;
			var currentOffset = $current.prop('offsetLeft') + $current.prop('clientWidth') * this._alignment;
			var startOffset = $start.prop('offsetLeft') + $start.prop('clientWidth') * this._alignment;
			var x = Math.round(-(currentOffset - startOffset) + this._offsetDrag);

			if ($current.prop('offsetParent')) {
				Utils.translateX(this.$inner[0], x);
			}

			this._needsUpdate = false;
		};

		Scooch.prototype.hideArrows = function(nextSlide) {
			this.$element.find('[data-m-slide=prev]').removeClass(this._getClass('inactive'));
			this.$element.find('[data-m-slide=next]').removeClass(this._getClass('inactive'));

			if (nextSlide === 1) {
				this.$element.find('[data-m-slide=prev]').addClass(this._getClass('inactive'));
			}

			if (nextSlide === this._length) {
				this.$element.find('[data-m-slide=next]').addClass(this._getClass('inactive'));
			}
		};

		Scooch.prototype.bind = function() {
			var abs = Math.abs;
			var dragging = false;
			var canceled = false;
			var dragRadius = this.options.dragRadius;
			var xy;
			var dx;
			var dy;
			var dragThresholdMet;
			var self = this;
			var $element = this.$element;
			var $inner = this.$inner;
			var opts = this.options;
			var lockLeft = false;
			var lockRight = false;
			var windowWidth = $(window).width();

			var start = function(e) {
				if (!has.touch) e.preventDefault();

				dragging = true;
				canceled = false;

				xy = Utils.getCursorPosition(e);
				dx = 0;
				dy = 0;
				dragThresholdMet = false;

				// Disable smooth transitions
				self._disableAnimation();

				lockLeft = self._index === self._lockLeft;
				lockRight = self._index === self._lockRight;
			};

			var drag = function(e) {
				if (!dragging || canceled) return;

				var newXY = Utils.getCursorPosition(e);
				var dragLimit = self.$element.width();
				dx = xy.x - newXY.x;
				dy = xy.y - newXY.y;

				if (dragThresholdMet || abs(dx) > abs(dy) && (abs(dx) > dragRadius)) {
					dragThresholdMet = true;
					e.preventDefault();

					if (lockLeft && (dx < 0)) {
						dx = dx * (-dragLimit) / (dx - dragLimit);
					} else if (lockRight && (dx > 0)) {
						dx = dx * (dragLimit) / (dx + dragLimit);
					}
					self._offsetDrag = -dx;
					self.update();
				} else if ((abs(dy) > abs(dx)) && (abs(dy) > dragRadius)) {
					canceled = true;
				}
			};

			var end = function(e) {
				if (!dragging) {
					return;
				}

				dragging = false;

				self._enableAnimation();

				if (!canceled && abs(dx) > opts.moveRadius) {
					// Move to the next slide if necessary
					if (opts.rightToLeft) {
						if (dx < 0) {
							self.next();
						} else {
							self.prev();
						}
					} else {
						if (dx > 0) {
							self.next();
						} else {
							self.prev();
						}
					}
				} else {
					// Reset back to regular position
					self._offsetDrag = 0;
					self.update();
				}
			};

			var click = function(e) {
				if (dragThresholdMet) e.preventDefault();
			};

			$inner
				.on(Utils.events.down + '.scooch', start)
				.on(Utils.events.move + '.scooch', drag)
				.on(Utils.events.up + '.scooch', end)
				.on('click.scooch', click)
				.on('mouseout.scooch', end);

			$element.on('click', '[data-m-slide]', function(e) {
				e.preventDefault();
				var action = $(this).attr('data-m-slide');
				var index = parseInt(action, 10);

				if (isNaN(index)) {
					self[action]();
				} else {
					self.move(index);
				}
			});

			$element.on('afterSlide', function(e, previousSlide, nextSlide) {
				self.$items.eq(previousSlide - 1).removeClass(self._getClass('active'));
				self.$items.eq(nextSlide - 1).addClass(self._getClass('active'));

				self.$element.find('[data-m-slide=\'' + previousSlide + '\']').removeClass(self._getClass('active'));
				self.$element.find('[data-m-slide=\'' + nextSlide + '\']').addClass(self._getClass('active'));

				if (opts.autoHideArrows) { // Hide prev/next arrows when at bounds
					self.hideArrows(nextSlide);
				}
			});

			$(window).on('resize orientationchange', function(e) {
				// Disable animation for now to avoid seeing
				// the carousel sliding, as it updates its position.
				// Animation will be enabled automatically when you're swiping.
				// Don't update Carousel on window height change
				if (windowWidth === $(window).width()) {
					return;
				}

				self._disableAnimation();
				windowWidth = $(window).width();
				self.update();
			});

			$element.trigger('beforeSlide', [1, 1]);
			$element.trigger('afterSlide', [1, 1]);
		};

		Scooch.prototype.unbind = function() {
			this.$inner.off();
		};

		Scooch.prototype.destroy = function() {
			this.unbind();
			this.$element.trigger('destroy');
			this.$element.remove();

			// Cleanup
			this.$element = null;
			this.$inner = null;
			this.$start = null;
			this.$current = null;
		};

		Scooch.prototype.move = function(newIndex, opts) {
			var $element = this.$element;
			var $inner = this.$inner;
			var $items = this.$items;
			var $start = this.$start;
			var $current = this.$current;
			var length = this._length;
			var index = this._index;

			opts = $.extend({}, this.options, opts);

			// Bound Values between [1, length];
			if (newIndex < this._lockLeft) {
				newIndex = this._lockLeft;
			} else if (newIndex > this._lockRight) {
				newIndex = this._lockRight;
			}

			// Check if we should animate
			if (opts.animate) {
				this._enableAnimation();
			} else {
				this._disableAnimation();
			}

			// Trigger beforeSlide event
			$element.trigger('beforeSlide', [index, newIndex]);

			// Index must be decremented to convert between 1- and 0-based indexing.
			if (opts.infinite) {
				this.$current = $current = $inner.children().eq(newIndex + this._loopPrepend - 1);
			} else {
				this.$current = $current = $items.eq(newIndex - 1);
			}

			this._offsetDrag = 0;
			this._index = newIndex;

			// Update, re-enable animation if necessary
			if (opts.animate) {
				this.update();
			} else {
				this.update(function() {
					this._enableAnimation();
				});
			}
			// Trigger afterSlide event
			if (opts.animate) {
				Utils.onTransitionEnd(this.$inner, function() {
					$element.trigger('afterSlide', [index, newIndex]);
				});
			} else {
				$element.trigger('afterSlide', [index, newIndex]);
			}
		};

		Scooch.prototype.next = function() {
			this.move(this._index + 1);
		};

		Scooch.prototype.prev = function() {
			this.move(this._index - 1);
		};

		return Scooch;

	})($, Utils);

	/**
		jQuery interface to set up a scooch carousel

		@param {String} [action] Action to perform. When no action is passed, the carousel is simply initialized.
		@param {Object} [options] Options passed to the action.
	*/
	$.fn.scooch = function(action, options) {
		var initOptions = $.extend({}, $.fn.scooch.defaults);

		// Handle different calling conventions
		if (typeof action === 'object') {
			$.extend(initOptions, action, true);
			options = null;
			action = null;
		}

		options = Array.prototype.slice.apply(arguments);

		this.each(function() {
			var $this = $(this);
			var scooch = this._scooch;

			if (!scooch) {
				scooch = new Scooch(this, initOptions);
			}

			if (action) {
				scooch[action].apply(scooch, options.slice(1));

				if (action === 'destroy') {
					scooch = null;
				}
			}

			this._scooch = scooch;
		});

		return this;
	};

	$.fn.scooch.defaults = {};

});